/*-------------------------------------------------------------------------
 *
 * nodeIndexscan.c
 *	  支持关系的索引扫描的例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeIndexscan.c
 *
 *-------------------------------------------------------------------------
 */
/*
 * 接口例程
 *		ExecIndexScan			使用索引扫描关系
 *		IndexNext				使用索引检索下一个元组
 *		IndexNextWithReorder	相同，但重新检查 ORDER BY 表达式
 *		ExecInitIndexScan		创建并初始化状态信息。
 *		ExecReScanIndexScan		重新扫描索引关系。
 *		ExecEndIndexScan		释放所有存储。
 *		ExecIndexMarkPos		标记扫描位置。
 *		ExecIndexRestrPos		恢复扫描位置。
 *		ExecIndexScanEstimate	估计并行索引扫描所需的 DSM 空间
 *		ExecIndexScanInitializeDSM 为并行索引扫描初始化 DSM
 *		ExecIndexScanReInitializeDSM 重新初始化 DSM 以进行新的扫描
 *		ExecIndexScanInitializeWorker 附加到并行工作中的 DSM 信息
 */
#include "postgres.h"

#include "access/nbtree.h"
#include "access/relscan.h"
#include "access/tableam.h"
#include "catalog/pg_am.h"
#include "executor/execdebug.h"
#include "executor/nodeIndexscan.h"
#include "lib/pairingheap.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "utils/array.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"

/*
 * 当使用排序操作符时，从索引中获取的需要重新排序的元组被排队在配对堆中，称为 ReorderTuples。
 */
typedef struct
{
	pairingheap_node ph_node;
	HeapTuple	htup;
	Datum	   *orderbyvals;
	bool	   *orderbynulls;
} ReorderTuple;

static TupleTableSlot *fc_IndexNext(IndexScanState *fc_node);
static TupleTableSlot *fc_IndexNextWithReorder(IndexScanState *fc_node);
static void fc_EvalOrderByExpressions(IndexScanState *fc_node, ExprContext *fc_econtext);
static bool fc_IndexRecheck(IndexScanState *fc_node, TupleTableSlot *fc_slot);
static int	fc_cmp_orderbyvals(const Datum *fc_adist, const bool *fc_anulls,
							const Datum *fc_bdist, const bool *fc_bnulls,
							IndexScanState *fc_node);
static int	fc_reorderqueue_cmp(const pairingheap_node *fc_a,
							 const pairingheap_node *fc_b, void *fc_arg);
static void fc_reorderqueue_push(IndexScanState *fc_node, TupleTableSlot *fc_slot,
							  Datum *fc_orderbyvals, bool *fc_orderbynulls);
static HeapTuple fc_reorderqueue_pop(IndexScanState *fc_node);


/* ----------------------------------------------------------------
 *		IndexNext
 *
 *		使用 IndexScanState 信息中指定的索引从 IndexScan 节点的 currentRelation 中检索元组。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_IndexNext(IndexScanState *fc_node)
{
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	ScanDirection fc_direction;
	IndexScanDesc fc_scandesc;
	TupleTableSlot *fc_slot;

	/*
	 * 从索引扫描节点提取必要信息
	 */
	fc_estate = fc_node->ss.ps.state;
	fc_direction = fc_estate->es_direction;
	/* 如果这是整体反向扫描，则翻转方向 */
	if (ScanDirectionIsBackward(((IndexScan *) fc_node->ss.ps.plan)->indexorderdir))
	{
		if (ScanDirectionIsForward(fc_direction))
			fc_direction = BackwardScanDirection;
		else if (ScanDirectionIsBackward(fc_direction))
			fc_direction = ForwardScanDirection;
	}
	fc_scandesc = fc_node->iss_ScanDesc;
	fc_econtext = fc_node->ss.ps.ps_ExprContext;
	fc_slot = fc_node->ss.ss_ScanTupleSlot;

	if (fc_scandesc == NULL)
	{
		/*
		 * 如果索引扫描不是并行的，或者我们正在串行执行计划为并行的索引扫描，则会到达此处。
		 */
		fc_scandesc = index_beginscan(fc_node->ss.ss_currentRelation,
								   fc_node->iss_RelationDesc,
								   fc_estate->es_snapshot,
								   fc_node->iss_NumScanKeys,
								   fc_node->iss_NumOrderByKeys);

		fc_node->iss_ScanDesc = fc_scandesc;

		/*
		 * 如果没有运行时键需要计算，或者它们已准备好，请继续将扫描键传递给索引 AM。
		 */
		if (fc_node->iss_NumRuntimeKeys == 0 || fc_node->iss_RuntimeKeysReady)
			index_rescan(fc_scandesc,
						 fc_node->iss_ScanKeys, fc_node->iss_NumScanKeys,
						 fc_node->iss_OrderByKeys, fc_node->iss_NumOrderByKeys);
	}

	/*
	 * 好的，现在我们已经拥有所需的内容，获取下一个元组。
	 */
	while (index_getnext_slot(fc_scandesc, fc_direction, fc_slot))
	{
		CHECK_FOR_INTERRUPTS();

		/*
		 * 如果索引是有损的，我们必须使用获取的元组重新检查索引资格。
		 */
		if (fc_scandesc->xs_recheck)
		{
			fc_econtext->ecxt_scantuple = fc_slot;
			if (!ExecQualAndReset(fc_node->indexqualorig, fc_econtext))
			{
				/* 重新检查失败，因此丢弃它并循环回去获取另一个 */
				InstrCountFiltered2(fc_node, 1);
				continue;
			}
		}

		return fc_slot;
	}

	/*
	 * 如果我们到这里，意味着索引扫描失败，因此我们在扫描的末尾。
	 */
	fc_node->iss_ReachedEnd = true;
	return ExecClearTuple(fc_slot);
}

/* ----------------------------------------------------------------
 *		IndexNextWithReorder
 *
 *		类似于 IndexNext，但此版本还可以重新检查 ORDER BY 表达式，并根据需要重新排序元组。
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_IndexNextWithReorder(IndexScanState *fc_node)
{
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	IndexScanDesc fc_scandesc;
	TupleTableSlot *fc_slot;
	ReorderTuple *fc_topmost = NULL;
	bool		fc_was_exact;
	Datum	   *fc_lastfetched_vals;
	bool	   *fc_lastfetched_nulls;
	int			fc_cmp;

	fc_estate = fc_node->ss.ps.state;

	/*
	 * 仅支持向前扫描与重新排序。注意：我们可以仅在此处进行断言，因为如果 ExecSupportsBackwardScan() 说它无法工作，系统将不会尝试反向运行计划。
	 * 目前，这是得到保证的，因为没有索引 AM 同时支持 amcanorderbyop 和 amcanbackward；如果将来有任何支持，
	 * ExecSupportsBackwardScan() 将需要明确考虑 indexorderbys。
	 */
	Assert(!ScanDirectionIsBackward(((IndexScan *) fc_node->ss.ps.plan)->indexorderdir));
	Assert(ScanDirectionIsForward(fc_estate->es_direction));

	fc_scandesc = fc_node->iss_ScanDesc;
	fc_econtext = fc_node->ss.ps.ps_ExprContext;
	fc_slot = fc_node->ss.ss_ScanTupleSlot;

	if (fc_scandesc == NULL)
	{
		/*
		 * 如果索引扫描不是并行的，或者我们正在串行执行计划为并行的索引扫描，则会到达此处。
		 */
		fc_scandesc = index_beginscan(fc_node->ss.ss_currentRelation,
								   fc_node->iss_RelationDesc,
								   fc_estate->es_snapshot,
								   fc_node->iss_NumScanKeys,
								   fc_node->iss_NumOrderByKeys);

		fc_node->iss_ScanDesc = fc_scandesc;

		/*
		 * 如果没有运行时键需要计算，或者它们已准备好，请继续将扫描键传递给索引 AM。
		 */
		if (fc_node->iss_NumRuntimeKeys == 0 || fc_node->iss_RuntimeKeysReady)
			index_rescan(fc_scandesc,
						 fc_node->iss_ScanKeys, fc_node->iss_NumScanKeys,
						 fc_node->iss_OrderByKeys, fc_node->iss_NumOrderByKeys);
	}

	for (;;)
	{
		CHECK_FOR_INTERRUPTS();

		/*
		 * 首先检查重新排序队列。如果队列中的最上面元组的 ORDER BY 值小于（或等于）索引最后返回的值，
		 * 我们可以立即返回它。
		 */
		if (!pairingheap_is_empty(fc_node->iss_ReorderQueue))
		{
			fc_topmost = (ReorderTuple *) pairingheap_first(fc_node->iss_ReorderQueue);

			if (fc_node->iss_ReachedEnd ||
				fc_cmp_orderbyvals(fc_topmost->orderbyvals,
								fc_topmost->orderbynulls,
								fc_scandesc->xs_orderbyvals,
								fc_scandesc->xs_orderbynulls,
								fc_node) <= 0)
			{
				HeapTuple	fc_tuple;

				fc_tuple = fc_reorderqueue_pop(fc_node);

				/* 传递 'true'，因为队列中的元组是 palloc 的副本 */
				ExecForceStoreHeapTuple(fc_tuple, fc_slot, true);
				return fc_slot;
			}
		}
		else if (fc_node->iss_ReachedEnd)
		{
			
/* 队列为空，并且索引中没有更多的元组。我们完成了。 */
			return ExecClearTuple(fc_slot);
		}

		 /*
		 * 从索引中获取下一个元组。
		 */
next_indextuple:
		if (!index_getnext_slot(fc_scandesc, ForwardScanDirection, fc_slot))
		{
			 /*
			 * 索引中没有更多元组。但我们仍然需要在完成之前，
			 * 清空队列中的任何剩余元组。
			 */
			fc_node->iss_ReachedEnd = true;
			continue;
		}

		 /*
		 * 如果索引是有损的，我们必须使用获取的元组重新检查索引条件和
		 * ORDER BY 表达式。
		 */
		if (fc_scandesc->xs_recheck)
		{
			fc_econtext->ecxt_scantuple = fc_slot;
			if (!ExecQualAndReset(fc_node->indexqualorig, fc_econtext))
			{
				/* 重新检查失败，因此丢弃它并循环回去获取另一个 */
				InstrCountFiltered2(fc_node, 1);
				/* 允许此循环可取消 */
				CHECK_FOR_INTERRUPTS();
				goto next_indextuple;
			}
		}

		if (fc_scandesc->xs_recheckorderby)
		{
			fc_econtext->ecxt_scantuple = fc_slot;
			ResetExprContext(fc_econtext);
			fc_EvalOrderByExpressions(fc_node, fc_econtext);

			 /*
			 * 索引返回的 ORDER BY 值是否准确？重新检查标志意味着索引可以返回不准确的值，
			 * 但是，任何特定元组返回的值也可能是完全正确的。将索引返回的值与重新计算的值进行比较。
			 * （如果索引返回的值恰好是准确的，我们通常可以避免将元组推入队列，然后再弹回出来。）
			 */
			fc_cmp = fc_cmp_orderbyvals(fc_node->iss_OrderByValues,
								  fc_node->iss_OrderByNulls,
								  fc_scandesc->xs_orderbyvals,
								  fc_scandesc->xs_orderbynulls,
								  fc_node);
			if (fc_cmp < 0)
				elog(ERROR, "index returned tuples in wrong order");
			else if (fc_cmp == 0)
				fc_was_exact = true;
			else
				fc_was_exact = false;
			fc_lastfetched_vals = fc_node->iss_OrderByValues;
			fc_lastfetched_nulls = fc_node->iss_OrderByNulls;
		}
		else
		{
			fc_was_exact = true;
			fc_lastfetched_vals = fc_scandesc->xs_orderbyvals;
			fc_lastfetched_nulls = fc_scandesc->xs_orderbynulls;
		}

		 /*
		 * 我们可以立即返回此元组，还是需要将其推入重排序队列？
		 * 如果索引返回的 ORDER BY 表达式值不准确，我们还无法返回它，因为
		 * 索引中的下一个元组可能需要在这个元组之前。此外，如果队列中已经有任何较小的元组，
		 * 我们也无法立即返回它。
		 */
		if (!fc_was_exact || (fc_topmost && fc_cmp_orderbyvals(fc_lastfetched_vals,
													  fc_lastfetched_nulls,
													  fc_topmost->orderbyvals,
													  fc_topmost->orderbynulls,
													  fc_node) > 0))
		{
			 /* 将此元组放入队列 */
			fc_reorderqueue_push(fc_node, fc_slot, fc_lastfetched_vals, fc_lastfetched_nulls);
			continue;
		}
		else
		{
			 /* 可以立即返回此元组。 */
			return fc_slot;
		}
	}

	/*
	 * 如果我们到这里，意味着索引扫描失败，因此我们在扫描的末尾。
	 */
	return ExecClearTuple(fc_slot);
}

 /*
 * 根据堆元组计算 ORDER BY 子句中的表达式。
 */
static void fc_EvalOrderByExpressions(IndexScanState *fc_node, ExprContext *fc_econtext)
{
	int			fc_i;
	ListCell   *fc_l;
	MemoryContext fc_oldContext;

	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	fc_i = 0;
	foreach(fc_l, fc_node->indexorderbyorig)
	{
		ExprState  *fc_orderby = (ExprState *) lfirst(fc_l);

		fc_node->iss_OrderByValues[fc_i] = ExecEvalExpr(fc_orderby,
												  fc_econtext,
												  &fc_node->iss_OrderByNulls[fc_i]);
		fc_i++;
	}

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * IndexRecheck -- 访问方法例程，以重新检查EvalPlanQual中的元组
 */
static bool fc_IndexRecheck(IndexScanState *fc_node, TupleTableSlot *fc_slot)
{
	ExprContext *fc_econtext;

	/*
	 * 从索引扫描节点提取必要信息
	 */
	fc_econtext = fc_node->ss.ps.ps_ExprContext;

	/* 元组是否满足indexqual条件？ */
	fc_econtext->ecxt_scantuple = fc_slot;
	return ExecQualAndReset(fc_node->indexqualorig, fc_econtext);
}


/*
 * 比较ORDER BY表达式值。
 */
static int fc_cmp_orderbyvals(const Datum *fc_adist, const bool *fc_anulls,
				const Datum *fc_bdist, const bool *fc_bnulls,
				IndexScanState *fc_node)
{
	int			fc_i;
	int			fc_result;

	for (fc_i = 0; fc_i < fc_node->iss_NumOrderByKeys; fc_i++)
	{
		SortSupport fc_ssup = &fc_node->iss_SortSupport[fc_i];

		/*
		 * 处理空值。我们只需支持NULLS LAST排序，
		 * 因为match_pathkeys_to_index()在其他情况下不考量indexorderby
		 * 的实现。
		 */
		if (fc_anulls[fc_i] && !fc_bnulls[fc_i])
			return 1;
		else if (!fc_anulls[fc_i] && fc_bnulls[fc_i])
			return -1;
		else if (fc_anulls[fc_i] && fc_bnulls[fc_i])
			return 0;

		fc_result = fc_ssup->comparator(fc_adist[fc_i], fc_bdist[fc_i], fc_ssup);
		if (fc_result != 0)
			return fc_result;
	}

	return 0;
}

/*
 * 配对堆提供获取最上面的（最大）元素，而KNN提供
 * 升序排序。这就是我们反转排序顺序的原因。
 */
static int fc_reorderqueue_cmp(const pairingheap_node *fc_a, const pairingheap_node *fc_b,
				 void *fc_arg)
{
	ReorderTuple *fc_rta = (ReorderTuple *) fc_a;
	ReorderTuple *fc_rtb = (ReorderTuple *) fc_b;
	IndexScanState *fc_node = (IndexScanState *) fc_arg;

	/* 交换参数顺序以反转排序顺序 */
	return fc_cmp_orderbyvals(fc_rtb->orderbyvals, fc_rtb->orderbynulls,
						   fc_rta->orderbyvals, fc_rta->orderbynulls,
						   fc_node);
}

/*
 * 辅助函数将元组推送到重新排序队列。
 */
static void fc_reorderqueue_push(IndexScanState *fc_node, TupleTableSlot *fc_slot,
				  Datum *fc_orderbyvals, bool *fc_orderbynulls)
{
	IndexScanDesc fc_scandesc = fc_node->iss_ScanDesc;
	EState	   *fc_estate = fc_node->ss.ps.state;
	MemoryContext fc_oldContext = MemoryContextSwitchTo(fc_estate->es_query_cxt);
	ReorderTuple *fc_rt;
	int			fc_i;

	fc_rt = (ReorderTuple *) palloc(sizeof(ReorderTuple));
	fc_rt->htup = ExecCopySlotHeapTuple(fc_slot);
	fc_rt->orderbyvals =
		(Datum *) palloc(sizeof(Datum) * fc_scandesc->numberOfOrderBys);
	fc_rt->orderbynulls =
		(bool *) palloc(sizeof(bool) * fc_scandesc->numberOfOrderBys);
	for (fc_i = 0; fc_i < fc_node->iss_NumOrderByKeys; fc_i++)
	{
		if (!fc_orderbynulls[fc_i])
			fc_rt->orderbyvals[fc_i] = datumCopy(fc_orderbyvals[fc_i],
										   fc_node->iss_OrderByTypByVals[fc_i],
										   fc_node->iss_OrderByTypLens[fc_i]);
		else
			fc_rt->orderbyvals[fc_i] = (Datum) 0;
		fc_rt->orderbynulls[fc_i] = fc_orderbynulls[fc_i];
	}
	pairingheap_add(fc_node->iss_ReorderQueue, &fc_rt->ph_node);

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * 辅助函数弹出重新排序队列中的下一个元组。
 */
static HeapTuple fc_reorderqueue_pop(IndexScanState *fc_node)
{
	HeapTuple	fc_result;
	ReorderTuple *fc_topmost;
	int			fc_i;

	fc_topmost = (ReorderTuple *) pairingheap_remove_first(fc_node->iss_ReorderQueue);

	fc_result = fc_topmost->htup;
	for (fc_i = 0; fc_i < fc_node->iss_NumOrderByKeys; fc_i++)
	{
		if (!fc_node->iss_OrderByTypByVals[fc_i] && !fc_topmost->orderbynulls[fc_i])
			pfree(DatumGetPointer(fc_topmost->orderbyvals[fc_i]));
	}
	pfree(fc_topmost->orderbyvals);
	pfree(fc_topmost->orderbynulls);
	pfree(fc_topmost);

	return fc_result;
}


/* ----------------------------------------------------------------
 *		ExecIndexScan(node)
 * ----------------------------------------------------------------
 */
static TupleTableSlot * fc_ExecIndexScan(PlanState *fc_pstate)
{
	IndexScanState *fc_node = castNode(IndexScanState, fc_pstate);

	/*
	 * 如果我们有运行时键，并且尚未设置，则现在进行设置。
	 */
	if (fc_node->iss_NumRuntimeKeys != 0 && !fc_node->iss_RuntimeKeysReady)
		ExecReScan((PlanState *) fc_node);

	if (fc_node->iss_NumOrderByKeys > 0)
		return ExecScan(&fc_node->ss,
						(ExecScanAccessMtd) fc_IndexNextWithReorder,
						(ExecScanRecheckMtd) fc_IndexRecheck);
	else
		return ExecScan(&fc_node->ss,
						(ExecScanAccessMtd) fc_IndexNext,
						(ExecScanRecheckMtd) fc_IndexRecheck);
}

/* ----------------------------------------------------------------
 *		ExecReScanIndexScan(node)
 *
 *		重新计算依赖于运行时已知信息的任何扫描键的值，然后
 *		重新扫描索引关系。
 *
 *		更新扫描键以前是在ExecUpdateIndexScanKeys中单独完成的。
 *		将其集成到ReScan使得索引和关系/一般流的重新扫描更加统一。
 * ----------------------------------------------------------------
 */
void ExecReScanIndexScan(IndexScanState *fc_node)
{
	/*
	 * 如果我们正在进行运行时键计算（即，任何索引键值都不是简单的常量），则计算新的键值。但是首先，重置上下文，以便在扫描每个外部元组时不会泄漏内存。请注意，这假定我们将在每次调用时重新计算*所有*运行时键。
	 */
	if (fc_node->iss_NumRuntimeKeys != 0)
	{
		ExprContext *fc_econtext = fc_node->iss_RuntimeContext;

		ResetExprContext(fc_econtext);
		ExecIndexEvalRuntimeKeys(fc_econtext,
								 fc_node->iss_RuntimeKeys,
								 fc_node->iss_NumRuntimeKeys);
	}
	fc_node->iss_RuntimeKeysReady = true;

	/* 刷新重新排序队列 */
	if (fc_node->iss_ReorderQueue)
	{
		HeapTuple	fc_tuple;

		while (!pairingheap_is_empty(fc_node->iss_ReorderQueue))
		{
			fc_tuple = fc_reorderqueue_pop(fc_node);
			heap_freetuple(fc_tuple);
		}
	}

	/* 重置索引扫描 */
	if (fc_node->iss_ScanDesc)
		index_rescan(fc_node->iss_ScanDesc,
					 fc_node->iss_ScanKeys, fc_node->iss_NumScanKeys,
					 fc_node->iss_OrderByKeys, fc_node->iss_NumOrderByKeys);
	fc_node->iss_ReachedEnd = false;

	ExecScanReScan(&fc_node->ss);
}


/*
 * ExecIndexEvalRuntimeKeys
 *		评估任何运行时键值，并更新scankeys。
 */
void ExecIndexEvalRuntimeKeys(ExprContext *fc_econtext,
						 IndexRuntimeKeyInfo *fc_runtimeKeys, int fc_numRuntimeKeys)
{
	int			fc_j;
	MemoryContext fc_oldContext;

	/* 我们希望将键值保存在每个元组内存中 */
	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	for (fc_j = 0; fc_j < fc_numRuntimeKeys; fc_j++)
	{
		ScanKey		fc_scan_key = fc_runtimeKeys[fc_j].scan_key;
		ExprState  *fc_key_expr = fc_runtimeKeys[fc_j].key_expr;
		Datum		fc_scanvalue;
		bool		fc_isNull;

		/*
		 * 对于每个运行时键，提取运行时表达式并根据当前上下文进行评估。
		 * 然后将结果放入适当的扫描键中。
		 *
		 * 注意：评估的结果可能是一个通过引用传递的值，存储在某个外部
		 * 扫描的元组中，而不是在
		 * econtext->ecxt_per_tuple_memory中。我们假设外部元组
		 * 在我们的扫描过程中保持不变。如果假设错误，我们可以显式地
		 * 将结果复制到我们的上下文中，但我认为这并不必要。
		 *
		 * 评估的结果也完全有可能是一个被烤制的值。在这种情况下，我们
		 * 应该强制解烤，以避免每次值被索引支持函数检查时重复解烤。
		 */
		fc_scanvalue = ExecEvalExpr(fc_key_expr,
								 fc_econtext,
								 &fc_isNull);
		if (fc_isNull)
		{
			fc_scan_key->sk_argument = fc_scanvalue;
			fc_scan_key->sk_flags |= SK_ISNULL;
		}
		else
		{
			if (fc_runtimeKeys[fc_j].key_toastable)
				fc_scanvalue = PointerGetDatum(PG_DETOAST_DATUM(fc_scanvalue));
			fc_scan_key->sk_argument = fc_scanvalue;
			fc_scan_key->sk_flags &= ~SK_ISNULL;
		}
	}

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * ExecIndexEvalArrayKeys
 *		评估任何数组键值，并设置遍历数组的准备。
 *
 * 如果有数组元素需要考虑，则返回true；如果存在至少一个空值或
 * 空数组，则返回false，这样就不可能匹配。在返回true结果时，
 * scankeys用数组的第一个元素初始化。
 */
bool ExecIndexEvalArrayKeys(ExprContext *fc_econtext,
					   IndexArrayKeyInfo *fc_arrayKeys, int fc_numArrayKeys)
{
	bool		fc_result = true;
	int			fc_j;
	MemoryContext fc_oldContext;

	/* 我们希望将数组保存在每个元组内存中 */
	fc_oldContext = MemoryContextSwitchTo(fc_econtext->ecxt_per_tuple_memory);

	for (fc_j = 0; fc_j < fc_numArrayKeys; fc_j++)
	{
		ScanKey		fc_scan_key = fc_arrayKeys[fc_j].scan_key;
		ExprState  *fc_array_expr = fc_arrayKeys[fc_j].array_expr;
		Datum		fc_arraydatum;
		bool		fc_isNull;
		ArrayType  *fc_arrayval;
		int16		fc_elmlen;
		bool		fc_elmbyval;
		char		fc_elmalign;
		int			fc_num_elems;
		Datum	   *fc_elem_values;
		bool	   *fc_elem_nulls;

		/*
		 * 计算并解构数组表达式。（在ExecIndexEvalRuntimeKeys()中的注释
		 * 也适用。）
		 */
		fc_arraydatum = ExecEvalExpr(fc_array_expr,
								  fc_econtext,
								  &fc_isNull);
		if (fc_isNull)
		{
			fc_result = false;
			break;				/* 没有必要评估更多 */
		}
		fc_arrayval = DatumGetArrayTypeP(fc_arraydatum);
		/* 我们可以缓存这些数据，但没有必要清除，值得付出代价 */
		get_typlenbyvalalign(ARR_ELEMTYPE(fc_arrayval),
							 &fc_elmlen, &fc_elmbyval, &fc_elmalign);
		deconstruct_array(fc_arrayval,
						  ARR_ELEMTYPE(fc_arrayval),
						  fc_elmlen, fc_elmbyval, fc_elmalign,
						  &fc_elem_values, &fc_elem_nulls, &fc_num_elems);
		if (fc_num_elems <= 0)
		{
			fc_result = false;
			break;				/* 没有必要评估更多 */
		}

		/*
		 * 注意：我们期望先前的数组数据（如果有）通过重置每个元组上下文
		 * 自动释放；因此这里没有pfree。
		 */
		fc_arrayKeys[fc_j].elem_values = fc_elem_values;
		fc_arrayKeys[fc_j].elem_nulls = fc_elem_nulls;
		fc_arrayKeys[fc_j].num_elems = fc_num_elems;
		fc_scan_key->sk_argument = fc_elem_values[0];
		if (fc_elem_nulls[0])
			fc_scan_key->sk_flags |= SK_ISNULL;
		else
			fc_scan_key->sk_flags &= ~SK_ISNULL;
		fc_arrayKeys[fc_j].next_elem = 1;
	}

	MemoryContextSwitchTo(fc_oldContext);

	return fc_result;
}

/*
 * ExecIndexAdvanceArrayKeys
 *		如有需要，推进到下一个数组键值集。
 *
 * 如果还有另一组值需要考虑，则返回true，否则返回false。在返回
 * true结果时，scankeys用下一组值初始化。
 */
bool ExecIndexAdvanceArrayKeys(IndexArrayKeyInfo *fc_arrayKeys, int fc_numArrayKeys)
{
	bool		fc_found = false;
	int			fc_j;

	/*
	 * 注意，我们最快速地推进最右边的数组键，因为它将
	 * 对应于可用条件中最低秩的索引列。这被假设为能带来更好的
	 * 索引访问局部性。
	 */
	for (fc_j = fc_numArrayKeys - 1; fc_j >= 0; fc_j--)
	{
		ScanKey		fc_scan_key = fc_arrayKeys[fc_j].scan_key;
		int			fc_next_elem = fc_arrayKeys[fc_j].next_elem;
		int			fc_num_elems = fc_arrayKeys[fc_j].num_elems;
		Datum	   *fc_elem_values = fc_arrayKeys[fc_j].elem_values;
		bool	   *fc_elem_nulls = fc_arrayKeys[fc_j].elem_nulls;

		if (fc_next_elem >= fc_num_elems)
		{
			fc_next_elem = 0;
			fc_found = false;		/* 需要前进到下一个数组键 */
		}
		else
			fc_found = true;
		fc_scan_key->sk_argument = fc_elem_values[fc_next_elem];
		if (fc_elem_nulls[fc_next_elem])
			fc_scan_key->sk_flags |= SK_ISNULL;
		else
			fc_scan_key->sk_flags &= ~SK_ISNULL;
		fc_arrayKeys[fc_j].next_elem = fc_next_elem + 1;
		if (fc_found)
			break;
	}

	return fc_found;
}


/* ----------------------------------------------------------------
 *		ExecEndIndexScan
 * ----------------------------------------------------------------
 */
void ExecEndIndexScan(IndexScanState *fc_node)
{
	Relation	fc_indexRelationDesc;
	IndexScanDesc fc_indexScanDesc;

	/*
	 * 从节点中提取信息
	 */
	fc_indexRelationDesc = fc_node->iss_RelationDesc;
	fc_indexScanDesc = fc_node->iss_ScanDesc;

	/*
	 * 释放exprcontext（们）……现在是死代码，请参见ExecFreeExprContext
	 */
#ifdef NOT_USED
	ExecFreeExprContext(&fc_node->ss.ps);
	if (fc_node->iss_RuntimeContext)
		FreeExprContext(fc_node->iss_RuntimeContext, true);
#endif

	/*
	 * 清空元组表插槽
	 */
	if (fc_node->ss.ps.ps_ResultTupleSlot)
		ExecClearTuple(fc_node->ss.ps.ps_ResultTupleSlot);
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	/*
	 * 关闭索引关系（如果我们没有打开它，则无操作）
	 */
	if (fc_indexScanDesc)
		index_endscan(fc_indexScanDesc);
	if (fc_indexRelationDesc)
		index_close(fc_indexRelationDesc, NoLock);
}

/* ----------------------------------------------------------------
 *		ExecIndexMarkPos
 *
 * 注意：我们假定没有调用者在读取至少一个元组之前尝试设置标记。
 * 否则，iss_ScanDesc可能仍然为NULL。
 * ----------------------------------------------------------------
 */
void ExecIndexMarkPos(IndexScanState *fc_node)
{
	EState	   *fc_estate = fc_node->ss.ps.state;
	EPQState   *fc_epqstate = fc_estate->es_epq_active;

	if (fc_epqstate != NULL)
	{
		/*
		 * 我们在EvalPlanQual重新检查中。如果该关系存在测试元组，则我们根本不应该访问索引。我们需要保存并稍后恢复relsubs_done标志的状态，以便能够重新获取测试元组。然而，考虑到没有调用者在扫描开始时设置标记的假设，我们仅在relsubs_done[i]已经设置时才能到达这里，因此无需保存状态。
		 */
		Index		fc_scanrelid = ((Scan *) fc_node->ss.ps.plan)->scanrelid;

		Assert(fc_scanrelid > 0);
		if (fc_epqstate->relsubs_slot[fc_scanrelid - 1] != NULL ||
			fc_epqstate->relsubs_rowmark[fc_scanrelid - 1] != NULL)
		{
			/* 验证上述声明 */
			if (!fc_epqstate->relsubs_done[fc_scanrelid - 1])
				elog(ERROR, "unexpected ExecIndexMarkPos call in EPQ recheck");
			return;
		}
	}

	index_markpos(fc_node->iss_ScanDesc);
}

/* ----------------------------------------------------------------
 *		ExecIndexRestrPos
 * ----------------------------------------------------------------
 */
void ExecIndexRestrPos(IndexScanState *fc_node)
{
	EState	   *fc_estate = fc_node->ss.ps.state;
	EPQState   *fc_epqstate = fc_estate->es_epq_active;

	if (fc_estate->es_epq_active != NULL)
	{
		/* 请参阅ExecIndexMarkPos中的注释 */
		Index		fc_scanrelid = ((Scan *) fc_node->ss.ps.plan)->scanrelid;

		Assert(fc_scanrelid > 0);
		if (fc_epqstate->relsubs_slot[fc_scanrelid - 1] != NULL ||
			fc_epqstate->relsubs_rowmark[fc_scanrelid - 1] != NULL)
		{
			/* 验证上述声明 */
			if (!fc_epqstate->relsubs_done[fc_scanrelid - 1])
				elog(ERROR, "unexpected ExecIndexRestrPos call in EPQ recheck");
			return;
		}
	}

	index_restrpos(fc_node->iss_ScanDesc);
}

/* ----------------------------------------------------------------
 *		ExecInitIndexScan
 *
 *		初始化索引扫描的状态信息，创建
 *		扫描键，并打开基础和索引关系。
 *
 *		注意：索引扫描有两组状态信息，因为
 *			  我们必须跟踪基础关系和
 *			  索引关系。
 * ----------------------------------------------------------------
 */
IndexScanState *
ExecInitIndexScan(IndexScan *fc_node, EState *fc_estate, int fc_eflags)
{
	IndexScanState *fc_indexstate;
	Relation	fc_currentRelation;
	LOCKMODE	fc_lockmode;

	/*
	 * 创建状态结构
	 */
	fc_indexstate = makeNode(IndexScanState);
	fc_indexstate->ss.ps.plan = (Plan *) fc_node;
	fc_indexstate->ss.ps.state = fc_estate;
	fc_indexstate->ss.ps.ExecProcNode = fc_ExecIndexScan;

	/*
	 * 杂项初始化
	 *
	 * 为节点创建表达式上下文
	 */
	ExecAssignExprContext(fc_estate, &fc_indexstate->ss.ps);

	/*
	 * 打开扫描关系
	 */
	fc_currentRelation = ExecOpenScanRelation(fc_estate, fc_node->scan.scanrelid, fc_eflags);

	fc_indexstate->ss.ss_currentRelation = fc_currentRelation;
	fc_indexstate->ss.ss_currentScanDesc = NULL;	/* 此处没有堆扫描 */

	/*
	 * 从关系描述符获取扫描类型。
	 */
	ExecInitScanTupleSlot(fc_estate, &fc_indexstate->ss,
						  RelationGetDescr(fc_currentRelation),
						  table_slot_callbacks(fc_currentRelation));

	/*
	 * 初始化结果类型和投影。
	 */
	ExecInitResultTypeTL(&fc_indexstate->ss.ps);
	ExecAssignScanProjectionInfo(&fc_indexstate->ss);

	
/*
	 * 初始化子表达式
	 *
	 * 注意：我们并不初始化所有的 indexqual 表达式，而只初始化
	 * 与运行时键对应的子部分（见下文）。同样，对于 indexorderby，若有的话。
	 * 但 indexqualorig 表达式总是被初始化，即使它只会在一些不常见的情况下使用 ---
	 * 希望能够改善这一点。（问题在于，表达式中存在的任何 SubPlans 必须现在找到……）
	 */
	fc_indexstate->ss.ps.qual =
		ExecInitQual(fc_node->scan.plan.qual, (PlanState *) fc_indexstate);
	fc_indexstate->indexqualorig =
		ExecInitQual(fc_node->indexqualorig, (PlanState *) fc_indexstate);
	fc_indexstate->indexorderbyorig =
		ExecInitExprList(fc_node->indexorderbyorig, (PlanState *) fc_indexstate);

	/*
	 * 如果我们只是执行 EXPLAIN（即，不打算运行计划），则在这里停止。
	 * 这允许索引建议插件 EXPLAIN 一个包含对不存在的索引引用的计划。
	 */
	if (fc_eflags & EXEC_FLAG_EXPLAIN_ONLY)
		return fc_indexstate;

	/* 打开索引关系。 */
	fc_lockmode = exec_rt_fetch(fc_node->scan.scanrelid, fc_estate)->rellockmode;
	fc_indexstate->iss_RelationDesc = index_open(fc_node->indexid, fc_lockmode);

	/*
	 * 初始化特定于索引的扫描状态
	 */
	fc_indexstate->iss_RuntimeKeysReady = false;
	fc_indexstate->iss_RuntimeKeys = NULL;
	fc_indexstate->iss_NumRuntimeKeys = 0;

	/*
	 * 从索引条件构建索引扫描键
	 */
	ExecIndexBuildScanKeys((PlanState *) fc_indexstate,
						   fc_indexstate->iss_RelationDesc,
						   fc_node->indexqual,
						   false,
						   &fc_indexstate->iss_ScanKeys,
						   &fc_indexstate->iss_NumScanKeys,
						   &fc_indexstate->iss_RuntimeKeys,
						   &fc_indexstate->iss_NumRuntimeKeys,
						   NULL,	/* 没有 ArrayKeys */  
						   NULL);

	/*
	 * 任何 ORDER BY 表达式都必须以相同的方式转换为 scankeys
	 */
	ExecIndexBuildScanKeys((PlanState *) fc_indexstate,
						   fc_indexstate->iss_RelationDesc,
						   fc_node->indexorderby,
						   true,
						   &fc_indexstate->iss_OrderByKeys,
						   &fc_indexstate->iss_NumOrderByKeys,
						   &fc_indexstate->iss_RuntimeKeys,
						   &fc_indexstate->iss_NumRuntimeKeys,
						   NULL,	/* 没有 ArrayKeys */  
						   NULL);

	/* 如果需要重新检查 ORDER BY 表达式，则初始化排序支持 */
	if (fc_indexstate->iss_NumOrderByKeys > 0)
	{
		int			fc_numOrderByKeys = fc_indexstate->iss_NumOrderByKeys;
		int			fc_i;
		ListCell   *fc_lco;
		ListCell   *fc_lcx;

		/*
		 * 准备排序支持，并查找每个 ORDER BY
		 * 表达式的数据类型。
		 */
		Assert(fc_numOrderByKeys == list_length(fc_node->indexorderbyops));
		Assert(fc_numOrderByKeys == list_length(fc_node->indexorderbyorig));
		fc_indexstate->iss_SortSupport = (SortSupportData *)
			palloc0(fc_numOrderByKeys * sizeof(SortSupportData));
		fc_indexstate->iss_OrderByTypByVals = (bool *)
			palloc(fc_numOrderByKeys * sizeof(bool));
		fc_indexstate->iss_OrderByTypLens = (int16 *)
			palloc(fc_numOrderByKeys * sizeof(int16));
		fc_i = 0;
		forboth(fc_lco, fc_node->indexorderbyops, fc_lcx, fc_node->indexorderbyorig)
		{
			Oid			fc_orderbyop = lfirst_oid(fc_lco);
			Node	   *fc_orderbyexpr = (Node *) lfirst(fc_lcx);
			Oid			fc_orderbyType = exprType(fc_orderbyexpr);
			Oid			fc_orderbyColl = exprCollation(fc_orderbyexpr);
			SortSupport fc_orderbysort = &fc_indexstate->iss_SortSupport[fc_i];

			/* 初始化排序支持 */
			fc_orderbysort->ssup_cxt = CurrentMemoryContext;
			fc_orderbysort->ssup_collation = fc_orderbyColl;
			/* 请参见 cmp_orderbyvals() 关于 NULLS LAST 的注释 */
			fc_orderbysort->ssup_nulls_first = false;
			/* ssup_attno 在这里和其他地方未使用 */
			fc_orderbysort->ssup_attno = 0;
			/* 无缩写 */
			fc_orderbysort->abbreviate = false;
			PrepareSortSupportFromOrderingOp(fc_orderbyop, fc_orderbysort);

			get_typlenbyval(fc_orderbyType,
							&fc_indexstate->iss_OrderByTypLens[fc_i],
							&fc_indexstate->iss_OrderByTypByVals[fc_i]);
			fc_i++;
		}

		/* 分配数组以保存重新计算的距离 */
		fc_indexstate->iss_OrderByValues = (Datum *)
			palloc(fc_numOrderByKeys * sizeof(Datum));
		fc_indexstate->iss_OrderByNulls = (bool *)
			palloc(fc_numOrderByKeys * sizeof(bool));

		/* 并初始化重排序队列 */
		fc_indexstate->iss_ReorderQueue = pairingheap_allocate(fc_reorderqueue_cmp,
															fc_indexstate);
	}

	/*
	 * 如果我们有运行时键，我们需要一个 ExprContext 来评估它们。节点的标准上下文不够用，因为我们想要在每个元组中重置该上下文。 
	 * 所以，构建另一个上下文，就像另一个一样...
	 * -tgl 7/11/00
	 */
	if (fc_indexstate->iss_NumRuntimeKeys != 0)
	{
		ExprContext *fc_stdecontext = fc_indexstate->ss.ps.ps_ExprContext;

		ExecAssignExprContext(fc_estate, &fc_indexstate->ss.ps);
		fc_indexstate->iss_RuntimeContext = fc_indexstate->ss.ps.ps_ExprContext;
		fc_indexstate->ss.ps.ps_ExprContext = fc_stdecontext;
	}
	else
	{
		fc_indexstate->iss_RuntimeContext = NULL;
	}

	/*
	 * 完成所有操作。
	 */
	return fc_indexstate;
}


/* 
 * ExecIndexBuildScanKeys
 * 	根据索引资格表达式构建索引扫描键
 *
 * 索引资格以 ScanKey 数组的形式传递给索引 AM。
 * 此例程设置 ScanKeys，填充 ScanKeys 的所有常量字段，并准备关于具有非常量比较值的键的信息。
 * 我们将索引资格表达式分为五种类型：
 *
 * 1. 带常量比较值的简单操作符（ "indexkey op constant"）。
 * 对于这些，我们只需填充一个包含常量值的 ScanKey。
 *
 * 2. 带非常量值的简单操作符（ "indexkey op expression"）。
 * 对于这些，我们创建一个 ScanKey，填充所有内容，除了表达式值，并设置一个 IndexRuntimeKeyInfo 结构
 * 在适当的时候推动表达式的评估。
 *
 * 3. RowCompareExpr（ "（indexkey，indexkey，...） op （expr，expr，...）"）。
 * 对于这些，我们创建一个头 ScanKey 以及一个附属的 ScanKey 数组，
 * 如 access/skey.h 中所指定。行比较的元素可以具有常量或非常量比较值。
 *
 * 4. ScalarArrayOpExpr（ "indexkey op ANY （array-expression）"）。如果索引
 * 支持 amsearcharray，我们将其与简单操作符一样处理，
 * 设置 SK_SEARCHARRAY 标志以告诉 AM 处理它们。否则，
 * 我们创建一个 ScanKey，填充除比较值以外的所有内容，
 * 并设置一个 IndexArrayKeyInfo 结构以推动符合条件的处理。
 * （请注意，如果我们使用 IndexArrayKeyInfo 结构，数组表达式
 * 总是被视为需要运行时评估，即使它是一个常量。）
 *
 * 5. NullTest（ "indexkey IS NULL/IS NOT NULL"）。我们只需正确填充
 * ScanKey。
 *
 * 此代码还用于准备用于 amcanorderbyop 的 ORDER BY 表达式
 * 索引。行为完全相同，只是我们必须以不同的方式查找
 * 操作符。请注意，目前只有案例 1 和 2 对于 ORDER BY 是可能的。
 *
 * 输入参数为：
 *
 * planstate：我们正在为其工作的执行器状态节点
 * index：我们为其构建扫描键的索引
 * quals：indexquals（或 indexorderbys）表达式
 * isorderby：如果处理ORDER BY exprs则为true，处理quals则为false
 * *runtimeKeys：指向现有 IndexRuntimeKeyInfos 的指针，如果没有则为 NULL
 * *numRuntimeKeys：现有运行时键的数量
 *
 * 输出参数为：
 *
 * *scanKeys：接收指向 ScanKeys 数组的指针
 * *numScanKeys：接收 scankeys 的数量
 * *runtimeKeys：接收指向 IndexRuntimeKeyInfos 数组的指针，或如果没有则为 NULL
 * *numRuntimeKeys：接收运行时键的数量
 * *arrayKeys：接收指向 IndexArrayKeyInfos 数组的指针，或如果没有则为 NULL
 * *numArrayKeys：接收数组键的数量
 *
 * 调用者可以为 arrayKeys 和 numArrayKeys 传递 NULL，表示
 * 不支持 IndexArrayKeyInfos。
 */
void ExecIndexBuildScanKeys(PlanState *fc_planstate, Relation fc_index,
					   List *fc_quals, bool fc_isorderby,
					   ScanKey *fc_scanKeys, int *fc_numScanKeys,
					   IndexRuntimeKeyInfo **fc_runtimeKeys, int *fc_numRuntimeKeys,
					   IndexArrayKeyInfo **fc_arrayKeys, int *fc_numArrayKeys)
{
	ListCell   *fc_qual_cell;
	ScanKey		fc_scan_keys;
	IndexRuntimeKeyInfo *fc_runtime_keys;
	IndexArrayKeyInfo *fc_array_keys;
	int			fc_n_scan_keys;
	int			fc_n_runtime_keys;
	int			fc_max_runtime_keys;
	int			fc_n_array_keys;
	int			fc_j;

	/* 为 ScanKey 结构分配数组：每个条件一个 */
	fc_n_scan_keys = list_length(fc_quals);
	fc_scan_keys = (ScanKey) palloc(fc_n_scan_keys * sizeof(ScanKeyData));

	/* 
	 * runtime_keys 数组根据需要动态调整大小。我们以这种方式处理它，
	 * 以便相同的运行时键数组可以在
	 * indexquals 和 indexorderbys 之间共享，这将在
	 * 此函数的单独调用中处理。调用者必须确保第一次
	 * 调用时传入 NULL/0。
	 */
	fc_runtime_keys = *fc_runtimeKeys;
	fc_n_runtime_keys = fc_max_runtime_keys = *fc_numRuntimeKeys;

	/* 将 array_keys 分配为可能需要的最大大小 */
	fc_array_keys = (IndexArrayKeyInfo *)
		palloc0(fc_n_scan_keys * sizeof(IndexArrayKeyInfo));
	fc_n_array_keys = 0;

	/* 
	 * 对于给定资格中的每个操作条件，将操作条件转换为单个
	 * 扫描键 */
	fc_j = 0;
	foreach(fc_qual_cell, fc_quals)
	{
		Expr	   *fc_clause = (Expr *) lfirst(fc_qual_cell);
		ScanKey		fc_this_scan_key = &fc_scan_keys[fc_j++];
		Oid			fc_opno;		/* 操作符的 OID */
		RegProcedure fc_opfuncid;	/* 用于扫描的操作符处理 ID */
		Oid			fc_opfamily;	/* 索引列的操作符族 */
		int			fc_op_strategy;	/* 操作符的策略编号 */
		Oid			fc_op_lefttype;	/* 操作符声明的输入类型 */
		Oid			fc_op_righttype;
		Expr	   *fc_leftop;		/* 操作符左侧的表达式 */
		Expr	   *fc_rightop;	/* 右侧的表达式... */
		AttrNumber	fc_varattno;	/* 在扫描中使用的属性编号 */
		int			fc_indnkeyatts;

		fc_indnkeyatts = IndexRelationGetNumberOfKeyAttributes(fc_index);
		if (IsA(fc_clause, OpExpr))
		{
			/* indexkey op const 或 indexkey op expression */
			int			fc_flags = 0;
			Datum		fc_scanvalue;

			fc_opno = ((OpExpr *) fc_clause)->opno;
			fc_opfuncid = ((OpExpr *) fc_clause)->opfuncid;

			/* 
			 * leftop 应该是索引键 Var，可能被重新标记 */
			fc_leftop = (Expr *) get_leftop(fc_clause);

			if (fc_leftop && IsA(fc_leftop, RelabelType))
				fc_leftop = ((RelabelType *) fc_leftop)->arg;

			Assert(fc_leftop != NULL);

			if (!(IsA(fc_leftop, Var) &&
				  ((Var *) fc_leftop)->varno == INDEX_VAR))
				elog(ERROR, "indexqual doesn't have key on left side");

			fc_varattno = ((Var *) fc_leftop)->varattno;
			if (fc_varattno < 1 || fc_varattno > fc_indnkeyatts)
				elog(ERROR, "bogus index qualification");

			/* 
			 * 我们需要查找操作符的策略编号。
			 * 这提供了一个交叉检查，确保操作符确实与索引匹配。
			 */
			fc_opfamily = fc_index->rd_opfamily[fc_varattno - 1];

			get_op_opfamily_properties(fc_opno, fc_opfamily, fc_isorderby,
									   &fc_op_strategy,
									   &fc_op_lefttype,
									   &fc_op_righttype);

			if (fc_isorderby)
				fc_flags |= SK_ORDER_BY;

			/* 
			 * rightop 是常量或变量比较值 */
			fc_rightop = (Expr *) get_rightop(fc_clause);

			if (fc_rightop && IsA(fc_rightop, RelabelType))
				fc_rightop = ((RelabelType *) fc_rightop)->arg;

			Assert(fc_rightop != NULL);

			if (IsA(fc_rightop, Const))
			{
				/* 好的，简单的常量比较值 */
				fc_scanvalue = ((Const *) fc_rightop)->constvalue;
				if (((Const *) fc_rightop)->constisnull)
					fc_flags |= SK_ISNULL;
			}
			else
			{
				/* 需要将这一项视为运行时键 */
				if (fc_n_runtime_keys >= fc_max_runtime_keys)
				{
					if (fc_max_runtime_keys == 0)
					{
						fc_max_runtime_keys = 8;
						fc_runtime_keys = (IndexRuntimeKeyInfo *)
							palloc(fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
					}
					else
					{
						fc_max_runtime_keys *= 2;
						fc_runtime_keys = (IndexRuntimeKeyInfo *)
							repalloc(fc_runtime_keys, fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
					}
				}
				fc_runtime_keys[fc_n_runtime_keys].scan_key = fc_this_scan_key;
				fc_runtime_keys[fc_n_runtime_keys].key_expr =
					ExecInitExpr(fc_rightop, fc_planstate);
				fc_runtime_keys[fc_n_runtime_keys].key_toastable =
					TypeIsToastable(fc_op_righttype);
				fc_n_runtime_keys++;
				fc_scanvalue = (Datum) 0;
			}

			/* 
			 * 适当地初始化扫描键的字段 */
			ScanKeyEntryInitialize(fc_this_scan_key,
								   fc_flags,
								   fc_varattno,	/* 要扫描的属性编号 */
								   fc_op_strategy, /* 操作符的策略 */
								   fc_op_righttype,	/* 策略子类型 */
								   ((OpExpr *) fc_clause)->inputcollid,	/* 排序规则 */
								   fc_opfuncid,	/* 使用的注册过程 */
								   fc_scanvalue);	/* constant */
		}
		else if (IsA(fc_clause, RowCompareExpr))
		{
			/* （indexkey，indexkey，...） op （expression，expression，...） */
			RowCompareExpr *fc_rc = (RowCompareExpr *) fc_clause;
			ScanKey		fc_first_sub_key;
			int			fc_n_sub_key;
			ListCell   *fc_largs_cell;
			ListCell   *fc_rargs_cell;
			ListCell   *fc_opnos_cell;
			ListCell   *fc_collids_cell;

			Assert(!fc_isorderby);

			fc_first_sub_key = (ScanKey)
				palloc(list_length(fc_rc->opnos) * sizeof(ScanKeyData));
			fc_n_sub_key = 0;

			/* 扫描 RowCompare 列并生成子扫描键项 */
			forfour(fc_largs_cell, fc_rc->largs, fc_rargs_cell, fc_rc->rargs,
					fc_opnos_cell, fc_rc->opnos, fc_collids_cell, fc_rc->inputcollids)
			{
				ScanKey		fc_this_sub_key = &fc_first_sub_key[fc_n_sub_key];
				int			fc_flags = SK_ROW_MEMBER;
				Datum		fc_scanvalue;
				Oid			fc_inputcollation;

				fc_leftop = (Expr *) lfirst(fc_largs_cell);
				fc_rightop = (Expr *) lfirst(fc_rargs_cell);
				fc_opno = lfirst_oid(fc_opnos_cell);
				fc_inputcollation = lfirst_oid(fc_collids_cell);

				/*
				 * leftop 应该是索引键 Var，可能被重新标记 */
				if (fc_leftop && IsA(fc_leftop, RelabelType))
					fc_leftop = ((RelabelType *) fc_leftop)->arg;

				Assert(fc_leftop != NULL);

				if (!(IsA(fc_leftop, Var) &&
					  ((Var *) fc_leftop)->varno == INDEX_VAR))
					elog(ERROR, "indexqual doesn't have key on left side");

				fc_varattno = ((Var *) fc_leftop)->varattno;

				/*
				 * 我们必须查找与操作符关联的 btree 支持
				 * 函数 */
				if (fc_index->rd_rel->relam != BTREE_AM_OID ||
					fc_varattno < 1 || fc_varattno > fc_indnkeyatts)
					elog(ERROR, "bogus RowCompare index qualification");
				fc_opfamily = fc_index->rd_opfamily[fc_varattno - 1];

				get_op_opfamily_properties(fc_opno, fc_opfamily, fc_isorderby,
										   &fc_op_strategy,
										   &fc_op_lefttype,
										   &fc_op_righttype);

				if (fc_op_strategy != fc_rc->rctype)
					elog(ERROR, "RowCompare index qualification contains wrong operator");

				fc_opfuncid = get_opfamily_proc(fc_opfamily,
											 fc_op_lefttype,
											 fc_op_righttype,
											 BTORDER_PROC);
				if (!RegProcedureIsValid(fc_opfuncid))
					elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
						 BTORDER_PROC, fc_op_lefttype, fc_op_righttype, fc_opfamily);

				/*
				 * rightop 是常量或变量比较值 */
				if (fc_rightop && IsA(fc_rightop, RelabelType))
					fc_rightop = ((RelabelType *) fc_rightop)->arg;

				Assert(fc_rightop != NULL);

				if (IsA(fc_rightop, Const))
				{
					/* 好的，简单的常量比较值 */
					fc_scanvalue = ((Const *) fc_rightop)->constvalue;
					if (((Const *) fc_rightop)->constisnull)
						fc_flags |= SK_ISNULL;
				}
				else
				{
					/* 需要将这一项视为运行时键 */
					if (fc_n_runtime_keys >= fc_max_runtime_keys)
					{
						if (fc_max_runtime_keys == 0)
						{
							fc_max_runtime_keys = 8;
							fc_runtime_keys = (IndexRuntimeKeyInfo *)
								palloc(fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
						}
						else
						{
							fc_max_runtime_keys *= 2;
							fc_runtime_keys = (IndexRuntimeKeyInfo *)
								repalloc(fc_runtime_keys, fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
						}
					}
					fc_runtime_keys[fc_n_runtime_keys].scan_key = fc_this_sub_key;
					fc_runtime_keys[fc_n_runtime_keys].key_expr =
						ExecInitExpr(fc_rightop, fc_planstate);
					fc_runtime_keys[fc_n_runtime_keys].key_toastable =
						TypeIsToastable(fc_op_righttype);
					fc_n_runtime_keys++;
					fc_scanvalue = (Datum) 0;
				}

				/*
				 * 适当地初始化子扫描键的字段 */
				ScanKeyEntryInitialize(fc_this_sub_key,
									   fc_flags,
									   fc_varattno,	/* 属性编号 */
									   fc_op_strategy, /* 操作符的策略 */
									   fc_op_righttype,	/* 策略子类型 */
									   fc_inputcollation,	/* 排序规则 */
									   fc_opfuncid,	/* 使用的注册过程 */
									   fc_scanvalue);	/* constant */
				fc_n_sub_key++;
			}

			/* 正确标记最后一个子扫描键 */
			fc_first_sub_key[fc_n_sub_key - 1].sk_flags |= SK_ROW_END;

			/*
			 * 我们不使用 ScanKeyEntryInitialize 作为头，因为它
			 * 不会包含有效的 sk_func 指针。 */
			MemSet(fc_this_scan_key, 0, sizeof(ScanKeyData));
			fc_this_scan_key->sk_flags = SK_ROW_HEADER;
			fc_this_scan_key->sk_attno = fc_first_sub_key->sk_attno;
			fc_this_scan_key->sk_strategy = fc_rc->rctype;
			/* sk_subtype, sk_collation, sk_func 在头部不使用 */
			fc_this_scan_key->sk_argument = PointerGetDatum(fc_first_sub_key);
		}
		else if (IsA(fc_clause, ScalarArrayOpExpr))
		{
			/* indexkey op ANY (数组表达式) */
			ScalarArrayOpExpr *fc_saop = (ScalarArrayOpExpr *) fc_clause;
			int			fc_flags = 0;
			Datum		fc_scanvalue;

			Assert(!fc_isorderby);

			Assert(fc_saop->useOr);
			fc_opno = fc_saop->opno;
			fc_opfuncid = fc_saop->opfuncid;

			/* 
			 * leftop 应该是索引键 Var，可能被重新标记 */
			fc_leftop = (Expr *) linitial(fc_saop->args);

			if (fc_leftop && IsA(fc_leftop, RelabelType))
				fc_leftop = ((RelabelType *) fc_leftop)->arg;

			Assert(fc_leftop != NULL);

			if (!(IsA(fc_leftop, Var) &&
				  ((Var *) fc_leftop)->varno == INDEX_VAR))
				elog(ERROR, "indexqual doesn't have key on left side");

			fc_varattno = ((Var *) fc_leftop)->varattno;
			if (fc_varattno < 1 || fc_varattno > fc_indnkeyatts)
				elog(ERROR, "bogus index qualification");

			/* 
			 * 我们需要查找操作符的策略编号。
			 * 这提供了一个交叉检查，确保操作符确实与索引匹配。
			 */
			fc_opfamily = fc_index->rd_opfamily[fc_varattno - 1];

			get_op_opfamily_properties(fc_opno, fc_opfamily, fc_isorderby,
									   &fc_op_strategy,
									   &fc_op_lefttype,
									   &fc_op_righttype);

			/*
			 * rightop 是常量或变量数组值 */
			fc_rightop = (Expr *) lsecond(fc_saop->args);

			if (fc_rightop && IsA(fc_rightop, RelabelType))
				fc_rightop = ((RelabelType *) fc_rightop)->arg;

			Assert(fc_rightop != NULL);

			if (fc_index->rd_indam->amsearcharray)
			{
				/* 索引 AM 将像简单操作符一样处理这个 */
				fc_flags |= SK_SEARCHARRAY;
				if (IsA(fc_rightop, Const))
				{
					/* 好的，简单的常量比较值 */
					fc_scanvalue = ((Const *) fc_rightop)->constvalue;
					if (((Const *) fc_rightop)->constisnull)
						fc_flags |= SK_ISNULL;
				}
				else
				{
					/* 需要将这一项视为运行时键 */
					if (fc_n_runtime_keys >= fc_max_runtime_keys)
					{
						if (fc_max_runtime_keys == 0)
						{
							fc_max_runtime_keys = 8;
							fc_runtime_keys = (IndexRuntimeKeyInfo *)
								palloc(fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
						}
						else
						{
							fc_max_runtime_keys *= 2;
							fc_runtime_keys = (IndexRuntimeKeyInfo *)
								repalloc(fc_runtime_keys, fc_max_runtime_keys * sizeof(IndexRuntimeKeyInfo));
						}
					}
					fc_runtime_keys[fc_n_runtime_keys].scan_key = fc_this_scan_key;
					fc_runtime_keys[fc_n_runtime_keys].key_expr =
						ExecInitExpr(fc_rightop, fc_planstate);

					/*
					 * 小心：运行时表达式不是
					 * op_righttype，而是相同的数组；所以
					 * TypeIsToastable() 没有帮助。然而，我们可以
					 * 假设所有数组类型都是可 toast 的。 */
					fc_runtime_keys[fc_n_runtime_keys].key_toastable = true;
					fc_n_runtime_keys++;
					fc_scanvalue = (Datum) 0;
				}
			}
			else
			{
				/* 执行器必须扩展数组值 */
				fc_array_keys[fc_n_array_keys].scan_key = fc_this_scan_key;
				fc_array_keys[fc_n_array_keys].array_expr =
					ExecInitExpr(fc_rightop, fc_planstate);
				/* 剩余字段由 palloc0 清零 */
				fc_n_array_keys++;
				fc_scanvalue = (Datum) 0;
			}

			/* 
			 * 适当地初始化扫描键的字段 */
			ScanKeyEntryInitialize(fc_this_scan_key,
								   fc_flags,
								   fc_varattno,	/* 要扫描的属性编号 */
								   fc_op_strategy, /* 操作符的策略 */
								   fc_op_righttype,	/* 策略子类型 */
								   fc_saop->inputcollid,	/* 排序规则 */
								   fc_opfuncid,	/* 使用的注册过程 */
								   fc_scanvalue);	/* constant */
		}
		else if (IsA(fc_clause, NullTest))
		{
			/* indexkey IS NULL 或 indexkey IS NOT NULL */
			NullTest   *fc_ntest = (NullTest *) fc_clause;
			int			fc_flags;

			Assert(!fc_isorderby);

			/*
			 * 参数应该是索引键 Var，可能被重新标记 */
			fc_leftop = fc_ntest->arg;

			if (fc_leftop && IsA(fc_leftop, RelabelType))
				fc_leftop = ((RelabelType *) fc_leftop)->arg;

			Assert(fc_leftop != NULL);

			if (!(IsA(fc_leftop, Var) &&
				  ((Var *) fc_leftop)->varno == INDEX_VAR))
				elog(ERROR, "NullTest indexqual has wrong key");

			fc_varattno = ((Var *) fc_leftop)->varattno;

			/* 
			 * 适当地初始化扫描键的字段 */
			switch (fc_ntest->nulltesttype)
			{
				case IS_NULL:
					fc_flags = SK_ISNULL | SK_SEARCHNULL;
					break;
				case IS_NOT_NULL:
					fc_flags = SK_ISNULL | SK_SEARCHNOTNULL;
					break;
				default:
					elog(ERROR, "unrecognized nulltesttype: %d",
						 (int) fc_ntest->nulltesttype);
					fc_flags = 0;	/* 保持编译器安静 */
					break;
			}

			ScanKeyEntryInitialize(fc_this_scan_key,
								   fc_flags,
								   fc_varattno,	/* 要扫描的属性编号 */
								   InvalidStrategy, /* 没有策略 */
								   InvalidOid,	/* 没有策略子类型 */
								   InvalidOid,	/* 没有排序 */
								   InvalidOid,	/* 没有注册过程 */
								   (Datum) 0);	/* constant */
		}
		else
			elog(ERROR, "unsupported indexqual type: %d",
				 (int) nodeTag(fc_clause));
	}

	Assert(fc_n_runtime_keys <= fc_max_runtime_keys);

	/* 清除任何未使用的数组 */
	if (fc_n_array_keys == 0)
	{
		pfree(fc_array_keys);
		fc_array_keys = NULL;
	}

	/*
	 * 将信息返回给调用者。
	 */
	*fc_scanKeys = fc_scan_keys;
	*fc_numScanKeys = fc_n_scan_keys;
	*fc_runtimeKeys = fc_runtime_keys;
	*fc_numRuntimeKeys = fc_n_runtime_keys;
	if (fc_arrayKeys)
	{
		*fc_arrayKeys = fc_array_keys;
		*fc_numArrayKeys = fc_n_array_keys;
	}
	else if (fc_n_array_keys != 0)
		elog(ERROR, "ScalarArrayOpExpr index qual found where not allowed");
}

/* ----------------------------------------------------------------
 *						并行扫描支持
 * ----------------------------------------------------------------
 */

/* ----------------------------------------------------------------
 *		ExecIndexScanEstimate
 *
 *		计算我们在并行
 *		查询 DSM 中需要的空间量，并通知 pcxt->estimator 我们的需求。
 * ----------------------------------------------------------------
 */
void ExecIndexScanEstimate(IndexScanState *fc_node,
					  ParallelContext *fc_pcxt)
{
	EState	   *fc_estate = fc_node->ss.ps.state;

	fc_node->iss_PscanLen = index_parallelscan_estimate(fc_node->iss_RelationDesc,
													 fc_estate->es_snapshot);
	shm_toc_estimate_chunk(&fc_pcxt->estimator, fc_node->iss_PscanLen);
	shm_toc_estimate_keys(&fc_pcxt->estimator, 1);
}

/* ----------------------------------------------------------------
 *		ExecIndexScanInitializeDSM
 *
 *		设置并行索引扫描描述符。
 * ----------------------------------------------------------------
 */
void ExecIndexScanInitializeDSM(IndexScanState *fc_node,
						   ParallelContext *fc_pcxt)
{
	EState	   *fc_estate = fc_node->ss.ps.state;
	ParallelIndexScanDesc fc_piscan;

	fc_piscan = shm_toc_allocate(fc_pcxt->toc, fc_node->iss_PscanLen);
	index_parallelscan_initialize(fc_node->ss.ss_currentRelation,
								  fc_node->iss_RelationDesc,
								  fc_estate->es_snapshot,
								  fc_piscan);
	shm_toc_insert(fc_pcxt->toc, fc_node->ss.ps.plan->plan_node_id, fc_piscan);
	fc_node->iss_ScanDesc =
		index_beginscan_parallel(fc_node->ss.ss_currentRelation,
								 fc_node->iss_RelationDesc,
								 fc_node->iss_NumScanKeys,
								 fc_node->iss_NumOrderByKeys,
								 fc_piscan);

	/*
	 * 如果没有需要计算的运行时键，或者它们已经准备好，继续将 scankeys 传递给索引 AM。
	 */
	if (fc_node->iss_NumRuntimeKeys == 0 || fc_node->iss_RuntimeKeysReady)
		index_rescan(fc_node->iss_ScanDesc,
					 fc_node->iss_ScanKeys, fc_node->iss_NumScanKeys,
					 fc_node->iss_OrderByKeys, fc_node->iss_NumOrderByKeys);
}

/* ----------------------------------------------------------------
 *		ExecIndexScanReInitializeDSM
 *
 *		开始新的扫描前重置共享状态。
 * ----------------------------------------------------------------
 */
void ExecIndexScanReInitializeDSM(IndexScanState *fc_node,
							 ParallelContext *fc_pcxt)
{
	index_parallelrescan(fc_node->iss_ScanDesc);
}

/* ----------------------------------------------------------------
 *		ExecIndexScanInitializeWorker
 *
 *		将相关信息从 TOC 复制到计划状态。
 * ----------------------------------------------------------------
 */
void ExecIndexScanInitializeWorker(IndexScanState *fc_node,
							  ParallelWorkerContext *fc_pwcxt)
{
	ParallelIndexScanDesc fc_piscan;

	fc_piscan = shm_toc_lookup(fc_pwcxt->toc, fc_node->ss.ps.plan->plan_node_id, false);
	fc_node->iss_ScanDesc =
		index_beginscan_parallel(fc_node->ss.ss_currentRelation,
								 fc_node->iss_RelationDesc,
								 fc_node->iss_NumScanKeys,
								 fc_node->iss_NumOrderByKeys,
								 fc_piscan);

	/*
	 * 如果没有需要计算的运行时键，或者它们已经准备好，继续将 scankeys 传递给索引 AM。
	 */
	if (fc_node->iss_NumRuntimeKeys == 0 || fc_node->iss_RuntimeKeysReady)
		index_rescan(fc_node->iss_ScanDesc,
					 fc_node->iss_ScanKeys, fc_node->iss_NumScanKeys,
					 fc_node->iss_OrderByKeys, fc_node->iss_NumOrderByKeys);
}
